package cn.iocoder.yudao.framework.region;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.text.csv.CsvRow;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.region.bo.RegionNode;
import cn.iocoder.yudao.framework.region.enums.RegionDataSet;
import cn.iocoder.yudao.framework.region.enums.RegionType;
import cn.iocoder.yudao.framework.region.vo.setter.CityAutoSetter;
import cn.iocoder.yudao.framework.region.vo.setter.CountyAutoSetter;
import cn.iocoder.yudao.framework.region.vo.setter.ProvinceAutoSetter;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 中国行政区域工具类
 *
 * @author 山野羡民（1032694760@qq.com）
 */
@SuppressWarnings({"unused", "SameReturnValue"})
@Slf4j
public class RegionUtils {
    private static final String OK_DATA_LEVEL_3_CSV = "ok_data_level3.csv";
    private static final String MCA_CSV = "mca.csv";
    private final Map<String, RegionNode> okDataLevel3Data = new ConcurrentHashMap<>(); // 内存缓存，提升访问速度
    private final Map<String, RegionNode> mcaData = new ConcurrentHashMap<>(); // 内存缓存，提升访问速度
    private final Lock lock = new ReentrantLock();

    private static class Holder {
        private static final RegionUtils INSTANCE = new RegionUtils();
    }

    private RegionUtils() {
        super();
    }

    /**
     * 获取地区数据
     *
     * @return 地区数据
     */
    public static Map<String, RegionNode> getRegionData(@Nonnull RegionDataSet dataSet) {
        Holder.INSTANCE.loadAndParseCsv(dataSet);
        if (dataSet == RegionDataSet.ONLY_LATEST) {
            return Holder.INSTANCE.okDataLevel3Data;
        }
        return Holder.INSTANCE.mcaData;
    }

    /**
     * 获取地区列表
     *
     * @return 列表
     */
    @SneakyThrows
    public static List<RegionNode> getRegionList(@Nonnull RegionDataSet dataSet) {
        List<CsvRow> csv = Holder.INSTANCE.loadDataFromCsvFile(dataSet);
        if (CollUtil.isEmpty(csv)) {
            throw new IllegalArgumentException("CSV 数据为空");
        }
        return Holder.INSTANCE.convertCsvRowsToRegionNodes(dataSet, csv);
    }

    /**
     * 获取地区列表
     *
     * @return 列表
     */
    @SneakyThrows
    public static List<RegionNode> getRegionList(@Nonnull RegionDataSet dataSet, String parentCode) {
        List<RegionNode> result = new ArrayList<>();
        List<RegionNode> regionNodes = getRegionList(dataSet);
        for (RegionNode regionNode : regionNodes) {
            if (StrUtil.equals(parentCode, regionNode.getParentCode())) {
                result.add(regionNode);
            }
        }
        return result;
    }

    /**
     * 获取地区树
     *
     * @return 树形结构
     */
    @SneakyThrows
    public static List<Tree<Integer>> getRegionTree(@Nonnull RegionDataSet dataSet) {
        List<RegionNode> nodes = getRegionList(dataSet);
        return TreeUtil.build(nodes, 0, (treeNode, tree) -> {
            tree.setId(NumberUtil.parseInt(treeNode.getCode()));
            tree.setParentId(NumberUtil.parseInt(treeNode.getParentCode()));
            tree.setWeight(treeNode.getCode());
            tree.setName(treeNode.getName());
        });
    }

    /**
     * 获得指定编号对应的区域
     *
     * @param id 区域编码
     * @return 区域
     */
    @Nullable
    public static RegionNode getRegionNode(@Nonnull RegionDataSet dataSet, Integer id) {
        return getRegionNode(dataSet, convertRegionIdToCode(id));
    }

    /**
     * 获得指定编号对应的区域
     *
     * @param code 区域编码
     * @return 区域
     */
    @Nullable
    public static RegionNode getRegionNode(@Nonnull RegionDataSet dataSet, String code) {
        if (StrUtil.isBlank(code)) {
            return null;
        }
        Holder.INSTANCE.loadAndParseCsv(dataSet);
        if (dataSet == RegionDataSet.ONLY_LATEST) {
            return Holder.INSTANCE.okDataLevel3Data.get(code);
        }
        return Holder.INSTANCE.mcaData.get(code);
    }

    /**
     * 根据省市区县名称获得省市区县编码编码
     *
     * @param provinceName 省份名称
     * @param cityName     城市名称
     * @param countyName   区县名称
     * @return 省市区县编码
     */
    @Nullable
    public static List<String> findRegionCodes(@Nonnull RegionDataSet dataSet, String provinceName, String cityName, String countyName) {
        Holder.INSTANCE.loadAndParseCsv(dataSet);
        String provinceCode = "";
        String cityCode = "";
        String countyCode = "";
        Map<String, RegionNode> data = getRegionData(dataSet);
        for (Map.Entry<String, RegionNode> entry : data.entrySet()) {
            RegionNode regionNode = entry.getValue();
            if (Objects.equals(regionNode.getType(), RegionType.PROVINCE.getValue()) && StrUtil.isNotBlank(provinceName) && (
                    provinceName.contains(regionNode.getName()) || regionNode.getName().contains(provinceName)
            )) {
                provinceCode = regionNode.getCode();
                log.debug("[findRegionCodes][provinceName({}) 匹配到省份({})]", provinceName, regionNode.getCode());
                break;
            }
        }
        for (Map.Entry<String, RegionNode> entry : data.entrySet()) {
            RegionNode regionNode = entry.getValue();
            if (Objects.equals(regionNode.getType(), RegionType.CITY.getValue()) && StrUtil.isNotBlank(cityName) && (
                    cityName.contains(regionNode.getName()) || regionNode.getName().contains(cityName)
            ) && regionNode.getParentCode().equals(provinceCode)) {
                cityCode = regionNode.getCode();
                log.debug("[findRegionCodes][cityName({}) 匹配到城市({})]", cityName, regionNode.getCode());
                break;
            }
        }
        for (Map.Entry<String, RegionNode> entry : data.entrySet()) {
            RegionNode regionNode = entry.getValue();
            if (Objects.equals(regionNode.getType(), RegionType.COUNTY.getValue()) && StrUtil.isNotBlank(cityName) && (
                    countyName.contains(regionNode.getName()) || regionNode.getName().contains(countyName)
            ) && regionNode.getParentCode().equals(cityCode)) {
                countyCode = regionNode.getCode();
                log.debug("[findRegionCodes][countyName({}) 匹配到区县({})]", countyName, regionNode.getCode());
                break;
            }
        }
        return CollUtil.newArrayList(provinceCode, cityCode, countyCode);
    }

    /**
     * 获得指定区域对应的编号
     *
     * @param pathStr   区域路径，例如说：河南省/石家庄市/新华区
     * @param separator 分隔符号，如“/”
     * @return 区域
     */
    @Nullable
    public static RegionNode parseRegion(@Nonnull RegionDataSet dataSet, String pathStr, String separator) {
        if (StrUtil.isBlank(pathStr)) {
            return null;
        }
        Holder.INSTANCE.loadAndParseCsv(dataSet);
        if (separator == null) {
            separator = "";
        }
        String[] paths = pathStr.split(separator);
        RegionNode regionNode = null;
        for (String path : paths) {
            if (regionNode == null) {
                regionNode = findFirst(getRegionData(dataSet).values(), item -> item.getName().equals(path));
            } else {
                regionNode = findFirst(regionNode.getChildren(), item -> item.getName().equals(path));
            }
        }
        return regionNode;
    }

    private static RegionNode findFirst(Collection<RegionNode> from, Predicate<RegionNode> predicate) {
        if (CollUtil.isEmpty(from)) {
            return null;
        }
        return from.stream().filter(predicate).findFirst().orElse(null);
    }

    /**
     * 格式化区域
     *
     * @param id        区域编码
     * @param separator 分隔符
     * @return 格式化后的区域
     */
    @Nullable
    public static String formatRegionName(@Nonnull RegionDataSet dataSet, Integer id, String separator) {
        return formatRegionName(dataSet, convertRegionIdToCode(id), separator);
    }

    /**
     * 格式化区域
     *
     * @param code      区域编码
     * @param separator 分隔符
     * @return 格式化后的区域
     */
    @Nullable
    public static String formatRegionName(@Nonnull RegionDataSet dataSet, String code, String separator) {
        if (StrUtil.isBlank(code)) {
            return null;
        }
        if (separator == null) {
            separator = "";
        }
        Holder.INSTANCE.loadAndParseCsv(dataSet);
        // 获得区域
        RegionNode regionNode = getRegionNode(dataSet, code);
        if (regionNode == null) {
            return null;
        }
        // 格式化
        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = RegionType.values().length; i < n; i++) { // 避免死循环
            sb.insert(0, regionNode.getName());
            String grandpaCode = paddingRegionCode(regionNode.getParentCode().substring(0, 2));
            // “递归”父节点
            regionNode = getRegionData(dataSet).get(regionNode.getParentCode());
            if (regionNode == null) {
                // 北京等直辖市下的区县可能没有城市级节点
                regionNode = getRegionData(dataSet).get(grandpaCode);
            }
            if (regionNode == null) {
                break;
            }
            if (Objects.equals(regionNode.getCode(), RegionNode.CODE_CHINA)) {
                // 跳过父节点为中国的情况
                break;
            }
            sb.insert(0, separator);
        }
        return sb.toString();
    }

    /**
     * 获取指定类型的区域列表
     *
     * @param type 区域类型
     * @param func 转换函数
     * @param <T>  结果类型
     * @return 区域列表
     */
    public static <T> List<T> getRegionListByType(@Nonnull RegionDataSet dataSet, RegionType type, Function<RegionNode, T> func) {
        List<RegionNode> regionNodes = Holder.INSTANCE.loadAndParseCsv(dataSet);
        if (CollUtil.isEmpty(regionNodes)) {
            return Collections.emptyList();
        }
        // 省直辖县级行政区划：419000、429000、469000
        // 县：500200
        // 自治区直辖县级行政区划：659000
        List<RegionNode> newRegionNodes = new ArrayList<>();
        for (RegionNode node : regionNodes) {
            if (Arrays.asList("419000", "429000", "469000", "500200", "659000")
                    .contains(node.getCode())) {
                continue;
            }
            if (node.getName().equals("市辖区")) {
                if ("110100".equals(node.getCode())) {
                    node.setName("北京市");
                    node.setShortName("北京");
                }
                if ("120100".equals(node.getCode())) {
                    node.setName("天津市");
                    node.setShortName("天津");
                }
                if ("310100".equals(node.getCode())) {
                    node.setName("上海市");
                    node.setShortName("上海");
                }
                if ("500100".equals(node.getCode())) {
                    node.setName("重庆市");
                    node.setShortName("重庆");
                }
            }
            newRegionNodes.add(node);
        }
        newRegionNodes.sort(Comparator.comparing(RegionNode::getAlphabet));
        return newRegionNodes.stream()
                .filter(regionNode -> Objects.equals(type.getValue(), regionNode.getType()))
                .map(func)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 根据区域编码、上级区域类型，获取上级区域编码
     *
     * @param id   区域编码
     * @return 上级区域编码
     */
    @Nullable
    public static Integer getRegionParentId(@Nonnull RegionDataSet dataSet, Integer id) {
        String parentCode = getRegionParentCode(dataSet, convertRegionIdToCode(id));
        if (parentCode == null) {
            return null;
        }
        return convertRegionCodeToId(parentCode);
    }

    public static int convertRegionCodeToId(String code) {
        code = StrUtil.trim(code, 1, character -> character.equals('0'));
        return NumberUtil.parseInt(code);
    }

    @Nullable
    public static String paddingRegionCode(String code) {
        if (StrUtil.isBlank(code)) {
            return null;
        }
        return StrUtil.padAfter(code, 6, '0');
    }

    public static String convertRegionIdToCode(Integer id) {
        if (id == null) {
            return null;
        }
        if (id == 0) {
            return "";
        }
        return paddingRegionCode(String.valueOf(id));
    }

    @Nullable
    public static Integer trimRegionId(Integer id, RegionType type) {
        if (id == null) {
            return null;
        }
        if (type == null) {
            return id;
        }
        if (type == RegionType.COUNTY) {
            return NumberUtil.parseInt(String.valueOf(id).substring(6));
        }
        if (type == RegionType.CITY) {
            return NumberUtil.parseInt(String.valueOf(id).substring(4));
        }
        if (type == RegionType.PROVINCE) {
            return NumberUtil.parseInt(String.valueOf(id).substring(2));
        }
        return id;
    }

    /**
     * 根据区域编码获取上级区域编码
     *
     * @param code 区域编码
     * @return 上级区域编码
     */
    @Nullable
    public static String getRegionParentCode(@Nonnull RegionDataSet dataSet, String code) {
        if (StrUtil.isBlank(code)) {
            return null;
        }
        RegionNode regionNode = getRegionNode(dataSet, code);
        if (regionNode == null) {
            return null;
        }
        return regionNode.getParentCode();
    }

    public static void autoAssignProvince(@Nonnull RegionDataSet dataSet, String provinceCode, ProvinceAutoSetter setter) {
        if (StrUtil.isBlank(provinceCode) || setter == null) {
            return;
        }
        setter.setProvinceCode(provinceCode);
        RegionNode regionNode = getRegionNode(dataSet, provinceCode);
        setter.setProvinceName(regionNode == null ? "" : regionNode.getName());
    }

    public static void autoAssignCity(@Nonnull RegionDataSet dataSet, String cityCode, CityAutoSetter setter) {
        if (StrUtil.isBlank(cityCode) || setter == null) {
            return;
        }
        setter.setCityCode(cityCode);
        RegionNode regionNode = getRegionNode(dataSet, cityCode);
        setter.setCityName(regionNode == null ? "" : regionNode.getName());
        String provinceCode = getRegionParentCode(dataSet, cityCode);
        if (StrUtil.isBlank(provinceCode)) {
            return;
        }
        autoAssignProvince(dataSet, provinceCode, setter);
    }

    public static void autoAssignCounty(@Nonnull RegionDataSet dataSet, String countyCode, CountyAutoSetter setter) {
        if (StrUtil.isBlank(countyCode) || setter == null) {
            return;
        }
        setter.setCountyCode(countyCode);
        RegionNode regionNode = getRegionNode(dataSet, countyCode);
        setter.setCountyName(regionNode == null ? "" : regionNode.getName());
        String cityCode = getRegionParentCode(dataSet, countyCode);
        if (StrUtil.isBlank(cityCode)) {
            return;
        }
        autoAssignCity(dataSet, cityCode, setter);
    }

    public static String formatDetailAddress(String provinceName, String cityName, String countyName, String detailAddress) {
        //noinspection DuplicatedCode
        if (provinceName == null) {
            provinceName = "";
        }
        if (cityName == null) {
            cityName = "";
        }
        if (countyName == null) {
            countyName = "";
        }
        if (detailAddress == null) {
            detailAddress = "";
        }
        if (StrUtil.isBlank(provinceName + cityName + countyName + detailAddress)) {
            return "";
        }
        // 移除详细地址开头的省市区县
        if (StrUtil.startWith(detailAddress, provinceName + cityName + countyName)) {
            detailAddress = detailAddress.replaceAll(provinceName + cityName + countyName, "");
        } else if (StrUtil.startWith(detailAddress, provinceName + cityName)) {
            detailAddress = detailAddress.replaceAll(provinceName + cityName, "");
        } else if (StrUtil.startWith(detailAddress, provinceName)) {
            detailAddress = detailAddress.replaceAll(provinceName, "");
        }
        return detailAddress;
    }

    public static String formatFullAddress(String provinceName, String cityName, String countyName, String detailAddress) {
        //noinspection DuplicatedCode
        if (provinceName == null) {
            provinceName = "";
        }
        if (cityName == null) {
            cityName = "";
        }
        if (countyName == null) {
            countyName = "";
        }
        if (detailAddress == null) {
            detailAddress = "";
        }
        return provinceName + cityName + countyName + formatDetailAddress(provinceName, cityName, countyName, detailAddress);
    }

    @SneakyThrows
    private List<CsvRow> loadDataFromCsvFile(@Nonnull RegionDataSet dataSet) {
        // 根据日志可知，此处存在并发问题，报错“IllegalStateException: End size 3617 is less than fixed size 3649”，所以加锁：
        // 2025-03-01 10:56:28.634 | [traceId:96063709-2757-41dd-b7f7-fae95145fc9d] DEBUG 21236 | http-nio-48080-exec-4 | c.i.y.f.r.RegionUtils/RegionUtils.java:371#loadCsv: 加载 CSV(ONLY_LATEST) 数据耗时 464 毫秒
        // 2025-03-01 10:56:28.634 | [traceId:14b2c3ef-026a-4665-87f0-1d0ccf8ebb22] DEBUG 21236 | http-nio-48080-exec-3 | c.i.y.f.r.RegionUtils/RegionUtils.java:371#loadCsv: 加载 CSV(ONLY_LATEST) 数据耗时 464 毫秒
        lock.lock();
        try {
            long createTime = System.currentTimeMillis();
            List<CsvRow> rows;
            if (dataSet == RegionDataSet.ONLY_LATEST) {
                rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader(OK_DATA_LEVEL_3_CSV)).getRows();
                // id,pid,deep,name,pinyin_prefix,pinyin,ext_id,ext_name
                CsvRow header = rows.remove(0);
                // CsvRow{originalLineNumber=0, fields=[﻿id, pid, deep, name, pinyin_prefix, pinyin, ext_id, ext_name]}
                //System.out.println(header);
                Assert.isTrue(header.getFieldCount() == 8 && header.get(4).equals("pinyin_prefix"), "CSV 文件已损坏，请检查");
            } else {
                rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader(MCA_CSV)).getRows();
                // name,code,parent_code,level,version
                CsvRow header = rows.remove(0);
                Assert.isTrue(header.getFieldCount() == 5 && header.get(2).equals("parent_code"), "CSV 文件已损坏，请检查");
            }
            log.debug("加载 CSV({}) 数据耗时 {} 毫秒", dataSet.name(), System.currentTimeMillis() - createTime);
            return rows;
        } finally {
            lock.unlock();
        }
    }

    @SneakyThrows
    private List<RegionNode> loadAndParseCsv(@Nonnull RegionDataSet dataSet) {
        lock.lock();
        try {
            if (dataSet == RegionDataSet.ONLY_LATEST) {
                if (CollUtil.isNotEmpty(okDataLevel3Data)) {
                    return okDataLevel3Data.values().stream().toList();
                }
            } else {
                if (CollUtil.isNotEmpty(mcaData)) {
                    return mcaData.values().stream().toList();
                }
            }
            long createTime = System.currentTimeMillis();
            List<CsvRow> csv = loadDataFromCsvFile(dataSet);
            if (CollUtil.isEmpty(csv)) {
                throw new IllegalArgumentException("CSV 数据为空");
            }
            List<RegionNode> nodes = convertCsvRowsToRegionNodes(dataSet, csv);
            log.debug("从 CSV({}) 解析行政区域数据成功，耗时 {} 毫秒", dataSet.name(), System.currentTimeMillis() - createTime);
            return nodes;
        } finally {
            lock.unlock();
        }
    }

    private List<RegionNode> convertCsvRowsToRegionNodes(@Nonnull RegionDataSet dataSet, List<CsvRow> csvRows) {
        if (dataSet == RegionDataSet.ONLY_LATEST) {
            okDataLevel3Data.clear();
        } else {
            mcaData.clear();
        }
        ArrayList<RegionNode> nodes = new ArrayList<>();
        if (CollUtil.isEmpty(csvRows)) {
            return nodes;
        }
        for (CsvRow row : csvRows) {
            RegionNode regionNode = new RegionNode();
            if (dataSet == RegionDataSet.ONLY_LATEST) {
                Assert.isTrue(row.getFieldCount() == 8, "CSV 数据格式不正确，请检查");
                // 11,0,0,"北京","b","bei jing","110000000000","北京市"
                regionNode.setCode(paddingRegionCode(row.get(0)));
                regionNode.setParentCode(paddingRegionCode(row.get(1)));
                regionNode.setType(Integer.valueOf(row.get(2)));
                regionNode.setName(row.get(7));
                regionNode.setShortName(row.get(3));
                regionNode.setPinyin(row.get(5));
                regionNode.setAlphabet(row.get(4).toUpperCase());
                okDataLevel3Data.put(regionNode.getCode(), regionNode);
            } else {
                Assert.isTrue(row.getFieldCount() == 5, "CSV 数据格式不正确，请检查");
                // "北京市","110000","000000",1,"2023"
                regionNode.setCode(paddingRegionCode(row.get(1)));
                regionNode.setParentCode(paddingRegionCode(row.get(2)));
                regionNode.setType(Integer.valueOf(row.get(3)));
                regionNode.setName(row.get(0));
                regionNode.setShortName(row.get(0));
                regionNode.setPinyin(PinyinUtils.toPinyin(regionNode.getName()));
                regionNode.setAlphabet(regionNode.getPinyin().substring(0, 1).toUpperCase());
                mcaData.put(regionNode.getCode(), regionNode);
            }
            nodes.add(regionNode);
        }
        return nodes;
    }

}
